package wviz.gui.graph;

import prefuse.Constants;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.activity.Activity;
import prefuse.controls.PanControl;
import prefuse.controls.ZoomControl;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.search.PrefixSearchTupleSet;
import prefuse.data.search.SearchTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.render.PolygonRenderer;
import prefuse.render.Renderer;
import prefuse.util.ColorLib;
import prefuse.visual.AggregateItem;
import prefuse.visual.AggregateTable;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;
import wviz.gui.DragControl;
import wviz.gui.color.NodeColorAction;
import wviz.gui.color.TextColorAction;
import wviz.gui.layout.AggregateLayout;
import wviz.gui.layout.T2vForceDirectedLayout;

import java.util.Iterator;
import java.awt.event.MouseEvent;


/**
 * This class creates a bubble-and-line graph representation of a set of
 * terms; node colour is based on term importance and links are created based
 * on co-occurrence relationships (node size depends on the number of links).
 */

public class SpiderDisplay extends BaseDisplay {

    // Minimum number of links required to be added to 'blobs'.
    // Set to zero to remove the corresponding 'blob'.
    public static final int OUTER_BLOB_THRESHOLD = 2;
    public static final int INNER_BLOB_THRESHOLD = 6;

    // Colours for the 'blobs' surrounding the most-linked nodes.
    private static final int INNER_BLOB_COLOUR = ColorLib.rgba(40, 160, 30, 40);
    private static final int OUTER_BLOB_COLOUR = ColorLib.rgba(40, 150, 30, 20);

    // Used for addressing specific graph elements in Prefuse.
    public static final String GRAPH = "graph";
    public static final String NODES = "graph.nodes";
    public static final String EDGES = "graph.edges";
    public static final String DOCUMENTS = "aggregates";

    static final double MAX_NODE_FONT_SIZE = 5;

    // This object drives the graph animation.
    private T2vForceDirectedLayout leoForce = new T2vForceDirectedLayout(GRAPH, true);

    // Constructor; initialize display and populate with data.
    public SpiderDisplay(Graph g) {
        this(g, 400, "Gravity");
    }

    public SpiderDisplay(Graph g, int filterMaxValue, String filterLabel) {
        super(new Visualization(), GRAPH, filterMaxValue, filterLabel, Visualization.SEARCH_ITEMS);
        setupGraph(g);
    }

    public void render(int numCategories) {
        // Draw nodes as rounded rectangles.
        LabelRenderer nodeR = new LabelRenderer("word");
        nodeR.setRoundedCorner(8, 8);

        // Draw aggregates ('blobs') as polygons with curved edges.
        Renderer polyR = new PolygonRenderer(Constants.POLY_TYPE_CURVE);
        //todo: CurveSlack value should be calculated by nodes inside the polygon to avoid 'bad-shaped' curves
        ((PolygonRenderer) polyR).setCurveSlack(0.05f);

        // Add node and aggregate renderes to .
        DefaultRendererFactory drf = new DefaultRendererFactory();
        drf.setDefaultRenderer(nodeR);
        drf.add("ingroup('aggregates')", polyR);
        m_vis.setRendererFactory(drf);

        // Set up colour definitions for various graph components.
        ColorAction nStroke = new ColorAction(NODES, VisualItem.STROKECOLOR);
        nStroke.setDefaultColor(ColorLib.gray(100));
        nStroke.add("_hover", ColorLib.gray(50));

        ColorAction nFill = new NodeColorAction(NODES);

        ColorAction nEdges = new ColorAction(EDGES, VisualItem.STROKECOLOR);
        nEdges.setDefaultColor(ColorLib.gray(100));

        ColorAction aStroke = new ColorAction(DOCUMENTS, VisualItem.STROKECOLOR);
        aStroke.setDefaultColor(ColorLib.gray(200));
        aStroke.add("_hover", ColorLib.rgb(255, 100, 100));

        int[] palette = new int[numCategories];
        for (int index = 0; index < numCategories; index++)
            palette[index] = index < (numCategories >>> 1) ? INNER_BLOB_COLOUR : OUTER_BLOB_COLOUR;
        ColorAction aFill = new DataColorAction(DOCUMENTS, "id", Constants.NOMINAL, VisualItem.FILLCOLOR, palette);

        ColorAction text = new TextColorAction(NODES);

        // Bundle the colours
        ActionList colors = new ActionList();
        colors.add(nStroke);
        colors.add(nFill);
        colors.add(nEdges);
        colors.add(aStroke);
        colors.add(aFill);
        colors.add(text);

        // Create all the key elements Prefuse needs for graph rendering.
        ActionList layout = new ActionList(8000);
        layout.add(leoForce);
        m_vis.putAction("layout", layout);

        ActionList dynamicActions = new ActionList(Activity.INFINITY);
        dynamicActions.add(colors);
        dynamicActions.add(new RepaintAction());
        dynamicActions.add(new AggregateLayout(DOCUMENTS));
        m_vis.putAction("dynamicActions", dynamicActions);

        // Enable mouse control (pan/zoom/drag).
        addControlListener(new DragControl() {
            @Override
            public void itemDragged(VisualItem item, MouseEvent e) {
                super.itemDragged(item, e);
                //m_vis.getAction("layout").setDuration(3000);
                m_vis.run("layout"); // re start layouting.
            }
        });
        addControlListener(new ZoomControl());
        addControlListener(new PanControl());

        // Set up the display.
        setSize(500, 500);
        pan(250, 250);
        setHighQuality(true);
        this.m_vis.run("layout"); // Start layouting.
        this.m_vis.run("dynamicActions"); // Start animation.
    }

    public void setFilterLevel(int level) {
        leoForce.adjust(level);
        this.m_vis.run("layout"); // re start layouting.
    }

    public void setupGraph(Graph g) {
        if (m_vis.getGroup(GRAPH) != null) {
            //group already added to this graph, remove it first
            m_vis.removeGroup(Visualization.SEARCH_ITEMS); //remove focus group
            m_vis.removeGroup(DOCUMENTS); //remove AggregateTable (i.e. blobs in visualisation)
            m_vis.removeGroup(GRAPH); //remove primary graph data nodes
        }
        VisualGraph vg = m_vis.addGraph(GRAPH, g);

        m_vis.setInteractive(EDGES, null, false);
        m_vis.setValue(NODES, null, VisualItem.SHAPE, Constants.SHAPE_DIAMOND);

        // Create the 'blobs' around nodes with lots of links.
        AggregateTable at = m_vis.addAggregates(DOCUMENTS);
        at.addColumn(VisualItem.POLYGON, float[].class);
        at.addColumn("id", int.class);

        AggregateItem aitem0 = (AggregateItem) at.addItem();
        aitem0.setInt("id", 0);
        AggregateItem aitem1 = (AggregateItem) at.addItem();
        aitem0.setInt("id", 1);

        Iterator itNodes = vg.nodes();
        Node node;
        while (itNodes.hasNext()) {
            node = (Node) itNodes.next();
            VisualItem visualItem = (VisualItem) node;
            if (node.getInt("links") > OUTER_BLOB_THRESHOLD)
                aitem0.addItem(visualItem);
            if (node.getInt("links") > INNER_BLOB_THRESHOLD)
                aitem1.addItem(visualItem);
        }

        render(2);

        // Define focus groups
        TupleSet selected = m_vis.getFocusGroup(Visualization.SELECTED_ITEMS);
        SearchTupleSet search = new PrefixSearchTupleSet();
        m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, search);

        // Hadi; not sure if this is actually required.
        selected.addTupleSetListener(new TupleSetListener() {
            public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
                m_vis.cancel("animate");
                m_vis.run("filter");
            }
        });

        // Modify the size of nodes depending on their number of links.
        TupleSet tupleSet = m_vis.getGroup(GRAPH);
        Iterator itr = tupleSet.tuples();
        
        while (itr.hasNext()) {
			Tuple tuple = (Tuple) itr.next();
			try {
				String word = tuple.getString("word");
				int links = tuple.getInt("links");
				if (word != null) {

					if (links > 6)
						tuple.setDouble(VisualItem.SIZE, 1.8);
					else if (links > 3)
						tuple.setDouble(VisualItem.SIZE, 1.4);
					else
						tuple.setDouble(VisualItem.SIZE, 1);					
				}

			} catch (Exception e) {
			}
        
        
//        while (itr.hasNext()) {
//            Tuple tuple = (Tuple) itr.next();
//            try {
//                String word = tuple.getString("word");
//                int links = tuple.getInt("links");
//                if (word != null) {
//                    tuple.setDouble(VisualItem.SIZE,
//                            Math.min(tuple.getDouble(VisualItem.SIZE) *
//                                    (1 + (links / 4.0)), MAX_NODE_FONT_SIZE));
//                }
//
//            } catch (Exception e) {
//            }
        }
    }
}